// *************************************
// * Creator:SunnyPaine
// * DateTime:2024/08/08 09:08:25
// * Description:<Description>
// * CopyRight:
// * ***********************************

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace SunnyPaine.Autofac.Extension.Mvvm
{
    /// <summary>
    /// 提供程序支持INotifyProperty接口，并能够通过GetProperty和SetProperty方法轻松实现可绑定属性。
    /// </summary>
    public abstract class NotifyPropertyChanged : INotifyPropertyChanged
    {
        private readonly ConcurrentDictionary<string, object> propertyDict = new ConcurrentDictionary<string, object>();

        /// <summary>
        /// 当属性变化时发生。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;


        #region 保护方法
        /// <summary>
        /// 当属性变化时通知。
        /// </summary>
        /// <param name="propertyName">属性名称。当为null时，将自动获取方法调用方的方法或属性的名称。</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// 当属性变化时通知。
        /// </summary>
        /// <param name="expression">返回属性信息的表达式。</param>
        /// <typeparam name="T">属性的类型。</typeparam>
        protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
        {
            this.OnPropertyChanged(GetPropertyName(expression));
        }

        /// <summary>
        /// 设置属性的值。
        /// </summary>
        /// <param name="value">属性新的值。</param>
        /// <param name="propertyName">属性名称。当为null时，将自动获取方法调用方的方法或属性的名称。</param>
        /// <typeparam name="T">属性的类型。</typeparam>
        /// <returns></returns>
        protected bool SetValue<T>(T value, [CallerMemberName] string propertyName = null)
        {
            return this.SetPropertyValue(propertyName, value);
        }

        /// <summary>
        /// 设置属性的值。
        /// </summary>
        /// <param name="value">属性新的值。</param>
        /// <param name="expression">返回属性信息的表达式。</param>
        /// <typeparam name="T">属性的类型。</typeparam>
        /// <returns></returns>
        protected bool SetValue<T>(T value, Expression<Func<T>> expression)
        {
            string propertyName = GetPropertyName(expression);
            return this.SetPropertyValue(propertyName, value);
        }

        /// <summary>
        /// 设置属性的值。
        /// </summary>
        /// <param name="value">属性新的值。</param>
        /// <param name="valueChangedCallback">
        /// 当值变化后的回调方法。
        /// <para>参数类型的KeyValuePair，Item1是新值，Item2是旧值。</para>
        /// </param>
        /// <param name="propertyName">属性名称。当为null时，将自动获取方法调用方的方法或属性的名称。</param>
        /// <typeparam name="T">属性的类型。</typeparam>
        /// <returns></returns>
        protected bool SetValue<T>(T value, Action<KeyValuePair<T, T>> valueChangedCallback, [CallerMemberName] string propertyName = null)
        {
            return this.SetPropertyValue(propertyName, value, valueChangedCallback);
        }

        /// <summary>
        /// 设置属性的值。
        /// </summary>
        /// <param name="value">属性新的值。</param>
        /// <param name="valueChangedCallback">
        /// 当值变化后的回调方法。
        /// <para>参数类型的KeyValuePair，Item1是新值，Item2是旧值。</para>
        /// </param>
        /// <param name="expression">返回属性信息的表达式。</param>
        /// <typeparam name="T">属性类型。</typeparam>
        /// <returns></returns>
        protected bool SetValue<T>(T value, Action<KeyValuePair<T, T>> valueChangedCallback, Expression<Func<T>> expression)
        {
            string propertyName = GetPropertyName(expression);
            return this.SetPropertyValue(propertyName, value, valueChangedCallback);
        }
        #endregion


        #region 私有方法
        private bool SetPropertyValue<T>(string propertyNam, T value, Action<KeyValuePair<T, T>> valueChangedCallback = null)
        {
            bool flag = this.SetPropertyValue(propertyNam, value, out T oldValue);
            if (flag)
            {
                valueChangedCallback?.Invoke(new KeyValuePair<T, T>(value, oldValue));
            }

            return flag;
        }

        private bool SetPropertyValue<T>(string propertyNam, T value, out T oldValue)
        {
            oldValue = default(T);
            if (this.propertyDict.TryGetValue(propertyNam, out object tmp))
            {
                oldValue = (T)tmp;
            }

            if (Equals(oldValue, value))
            {
                return false;
            }

            this.propertyDict[propertyNam] = value;
            this.OnPropertyChanged(propertyNam);
            return true;
        }

        private static string GetPropertyName(LambdaExpression expression)
        {
            MemberExpression memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("MemberExpression s expected in expression.Body", "expression");
            }

            MemberInfo memberInfo = memberExpression.Member;
            if (memberInfo.MemberType == MemberTypes.Field && memberInfo.Name.StartsWith("$VB$Local_"))
            {
                return memberInfo.Name.Substring("$VB$Local_".Length);
            }

            return memberInfo.Name;
        }
        #endregion
    }
}